#title: 文件上传
#index:0,1
#author: zozoh(zozohtnt@gmail.com)
#author: wendal(wendal1985@gmail.com)
----------------------------------------------------------------------------------------------
关于文件上传
	
	大多数的 Web 应用都不可避免的，会涉及到文件上传。文件上传，不过是一种适配 HTTP 输入流的方式。
	为此，Nutz.Mvc 内置了一个专门处理文件上传的适配器 {*org.nutz.mvc.upload.UploadAdaptor}

    这个适配器专门解析形式为
    {{{<html>
    <form target="hideWin" enctype="multipart/form-data" method="post">
        <input type="file" name="photo">
        ...
    }}}
    的 HTML 提交表单
	
	这个适配器的工作细节是这样的：
	 * 它一次将 HTTP 输入流中所有的文件读入，保存在[../lang/filepool.man 临时文件目录]里
	 * 表单项目会保存在内存里
	 * 在上传的过程中，它会向当前 Session 中设置一个对象： {*org.nutz.mvc.upload.UploadInfo}
		 * 属性名为 "org.nutz.mvc.upload.UploadInfo"
		 * 通过静态函数 Uploads.getInfo(HttpServletRequest req) 可以很方便的获取当前会话的 UploadInfo
	 * 不断的读入输入流的过程，会记录在 UploadInfo 里面。
		 * UploadInfo 字段 sum 是当前 HTTP 请求的 ContentLength，表示 HTTP 输入流总长度为多少字节
		 * UploadInfo 字段 current 是当前会话已经上传了多少字节
     * 如果使用ajax进行文件上传,请使用webuploader等js插件.
----------------------------------------------------------------------------------------------
如何使用

	如果你读过 [http_adaptor.man 适配器] 一节，我想你已经知道怎么使用文件上传了，这里还需要多一点说明
	------------------------------------------------------------------------------------------
	通过 @AdaptBy 声明
		{{{<JAVA>
		...
		@AdaptBy(type = UploadAdaptor.class, args = { "${app.root}/WEB-INF/tmp" })
		public void uploadPhoto(@Param("id") int id, @Param("photo") File f){
			...
		}}}
        Nutz.Mvc 会为你的入口函数创建一个上传适配器，上传适配器的会将临时文件存放在
         {*/WEB-INF/tmp} 目录下。 请为不同的入口方法分配不同的临时文件夹路径
         请为不同的入口方法分配不同的{*临时文件路径*}哦
         
         app.root代表web项目的根路径, 也就是`servletContext.getRealPath("/")`所返回的值
		
	------------------------------------------------------------------------------------------
    更细腻的控制
        
        更多的时候，你想控制
         * 上传文件的临时文件数量
         * 可以上传的单个文件最大尺寸
         * 文件的类型
         * 空文件跳过不保存
        那么你需要把上传适配器交由 Ioc 容器来管理，首先你的入口函数需要这么写：
        {{{<JAVA>
		...
		@AdaptBy(type = UploadAdaptor.class, args = { "ioc:myUpload" })
		public void uploadPhoto(@Param("id") int id, @Param("photo") File f){
            ...
        }}}
        请注意这里的 "ioc:myUpload"，Nutz.Mvc 发现你的参数为此形式时，它会从 Ioc 容器里获取你的
        上传适配器的实例（名字为{*myUpload}），而不是直接 new 出来, 而且必须为singleton=false。

        然后你需要在你的 Ioc 配置文件里 （以 Json 配置为例子）配置你的 UploadAdaptor, 你需要增加
        下面这三个对象：
        {{{<JS>
        ...
        tmpFilePool : {
            type : 'org.nutz.filepool.NutFilePool',
            // 临时文件最大个数为 1000 个
            args : [ "~/nutz/blog/upload/tmps", 1000 ]   
        },
        uploadFileContext : {
            type : 'org.nutz.mvc.upload.UploadingContext',
            singleton : false,
            args : [ { refer : 'tmpFilePool' } ],
            fields : {
                // 是否忽略空文件, 默认为 false
                ignoreNull : true,
                // 单个文件最大尺寸(大约的值，单位为字节，即 1048576 为 1M)
                maxFileSize : 1048576,
                // 正则表达式匹配可以支持的文件名
                nameFilter : '^(.+[.])(gif|jpg|png)$' 
            } 
        },
        myUpload : {
            type : 'org.nutz.mvc.upload.UploadAdaptor',
            singleton : false,
            args : [ { refer : 'uploadFileContext' } ] 
        }
        ...
        }}}
        这样，在配置文件中，你就可以随心所欲的进行控制了。而且这么写仅仅多写了不到 20 行的配置文件，
        到也没有特别麻烦，对吗？（相关详细规则描述：[http_adaptor.man#通过_Ioc_容器获得适配器]）

        {*#F00;特别注意} 有的同学使用文件上传遇到了
        [https://github.com/nutzam/nutz/issues/575 诡异的问题]
        如果你要用 Ioc 来管理 UploadAdaptor，你必须为不同的入口函数分配不同的实例。
        当然这些实例可以引用同样的 `uploadFileContext` 和 `tmpFilePool`。
        否则我们不能保证适配的结果正确。
        
        看到这里，有些同学会问了，为什么你用注解的方式可以这么写：
        {{{<JAVA>
		@AdaptBy(type = UploadAdaptor.class, args = { "${app.root}/WEB-INF/tmp" })
        }}}
        '{*`{app.root}`}' 用起来多方便啊，那么在 Ioc 容器来，我的临时目录还想放在这里，有没有
        什么办法啊？ 

        办法有。但是因为 Ioc 容器原来设计的是同 Mvc 的 Web 容器环境不相干的，所以要想绕过了这点
        限制，会稍微的有一点麻烦。

        首先，因为 Ioc 容器设计的良好扩展性，所有实现了 '{*org.nutz.ioc.Ioc2}' 这个接口的 Ioc
        容器（当然，Nutz 自带的 Ioc 容器 - NutzIoc，很好的实现了 Ioc2 这个接口）都可以被 Nutz.Mvc
        注入一种新的获取值的方式（详情请参看 [with_ioc.man#在容器对象里获得_ServletContext]),
        我想你只需要实现一个简单的 Java 对象即可:
        {{{<Java>
        package com.abc.myapp;

        import javax.servlet.ServletContext;

        public class MyUtils {

            private ServletContext sc;

            public String getPath(String path) {
                return sc.getRealPath(path);
            }
        }

        }}}
        然后，你可以在 Ioc 容器的配置文件里，增一个对象，并修改 'tmpFilePool' 对象的配置信息：
        {{{<JS>
        utils : {
            type : 'com.abc.myapp.MyUtils',
            fields : {
                sc : {app:'$servlet'}   // 将 ServletContext 对象注入 MyUtils
            }
        },

        tmpFilePool : {
            type : 'org.nutz.filepool.NutFilePool',
            args : [ {java:'$utils.getPath("/WEB-INF/tmp")'}, 1000 ]   // 调用 MyUtils.getPath 函数
        },
        }}}


------------------------------------------------------------------------------------------
在入口函数的参数里获得上传的文件

        因为 FORM 表单里的 input type=file 项都已经被 UploadAdaptor 解析并存入服务器的临时目录
        （你声明的那个目录，比如上例是 /WEB-INF/tmp），你在入口函数里可以直接获取：
        {{{<JAVA>
		@AdaptBy(type = UploadAdaptor.class, args = { "ioc:myUpload" })
		public void uploadPhoto( @Param("photo") File f){
            ...
        }}}
        你的参数 f 将获取到表单项中:
        {{{<html>
        <form target="hideWin" enctype="multipart/form-data" method="post">
            <input type="file" name="photo">
            ...
        }}}
        中的文件内容。你直接打开一个 InputStream 就能从这个 File 中读取客户端上传的文件内容了。

        但是，如果你也想知道这个文件在客户端原本的名字，怎么办呢？你可以：
        {{{<JAVA>
		@AdaptBy(type = UploadAdaptor.class, args = { "ioc:myUpload" })
		public void uploadPhoto( @Param("photo") TempFile tf){
            File f = tf.getFile();                       // 这个是保存的临时文件
            FieldMeta meta = tf.getMeta();               // 这个原本的文件信息
            String oldName = meta.getFileLocalName();    // 这个时原本的文件名称
            // TODO do what you wanna to do here ...
        }
        }}}

------------------------------------------------------------------------------------------
一次上传多个同名文件

        相信很多同学都会有这样的用法，就是，希望一次上传的表单为：
        {{{
        <form target="hideWin" enctype="multipart/form-data" method="post">
            <input type="file" name="photo">
            <input type="file" name="photo">
            <input type="file" name="photo">
            ...
        }}}
        看，一个上传表单中带有多个同名文件，那么怎么办呢？ 你在入口函数里可以这么声明：
        {{{
		@AdaptBy(type = UploadAdaptor.class, args = { "ioc:myUpload" })
		public void uploadPhoto( @Param("photo") TempFile[] tfs){
            ...
        }
        }}}
        是的，它们会被变成一个数组。顺序同 `<form>` 中的顺序。

        但是这里，有一点{#F00;*限制}
         * {#F00;* 你只能用 TempFile`[`]}
        也就是说，如果你用了 `File[]` 将会出错。

-------------------------------------------------------------------------------------
可用的参数类型

	* TempFile 及 TempFile`[`]
	* File 推荐用TempFile
	* Map 用于得到全部参数 
	
-------------------------------------------------------------------------------------
AJAX 方式上传文件

    现在的浏览器配合 jQuery 的话是很方便上传文件的，例子如下，省略 html 代码

    {{{<JS>
    <script>
        $(document).ready(function () {
            $("#upload").click(function () {
                var formData = new FormData();
                formData.append('file', $('#file')[0].files[0]);

                $.ajax({
                    url: "/upload",
                    method: 'POST',
                    data: formData,
                    processData: false,
                    contentType: false,
                    success: function (data) {
                        console.log("success");
                    }
                });
            });
        });
    </script>
    }}}

    {{{<JAVA>
    @At("/upload")
    @AdaptBy(type = UploadAdaptor.class, args = {"${app.root}/WEB-INF/tmp"})
    public void uploadPhoto(@Param("file") File f) {
        System.out.println(f.getName());
    }
    }}}

    其实这就是在画面生成一个 form 对象，然后用 AJAX POST 方式来提交这个 form。是不是一下让你想到远古时期用 js 提交 form 表单的做法呢。
-------------------------------------------------------------------------------------
推荐的js插件

	* [http://fex.baidu.com/webuploader/ webuploader] 百毒出品,体验还是不错的
	* [https://github.com/codler/jQuery-Ajax-Upload jQuery-Ajax-Upload] 老牌ajax上传插件
	
-------------------------------------------------------------------------------------
直接上传到云

	以七牛云存储为例, 使用其js-sdk及java-sdk
	
	其中,java-sdk用于提供uptoken的入口方法
	
	{{{<JAVA>
    @Ok("json:full")
    @At
    public NutMap uptoken() {
        String accessKey = conf.get("qiniu.accessKey");
        String secretKey = conf.get("qiniu.secretKey");
        String bucketName = conf.get("qiniu.bucketName");
        Auth auth = Auth.create(accessKey, secretKey);
        //StringMap policy = new StringMap(); // 可选,详情请查阅七牛的文档,建议限定上传路径
        StringMap policy = null;
        int timeout = 600;
        String token = auth.uploadToken(bucketName, null, timeout, policy);
        return new NutMap("uptoken", token);
    }
	}}}
	
	供参考的js代码
	
	{{{<js>
	var uploader = Qiniu.uploader({
		runtimes : 'html5,flash,html4', // 上传模式，依次退化
		browse_button : 'pickfiles', // 上传选择的点选按钮，必需
		uptoken_url : base + '/upload/uptoken', // Ajax请求uptoken的Url，强烈建议设置（服务端提供）
		get_new_uptoken : true, // 设置上传文件的时候是否每次都重新获取新的uptoken
		max_file_size : '128mb', // 最大文件体积限制
		max_retries : 3, // 上传失败最大重试次数
		// dragdrop: true, // 开启可拖曳上传
		// drop_element: 'container', // 拖曳上传区域元素的ID，拖曳文件或文件夹后可触发上传
		chunk_size : '4mb', // 分块上传时，每块的体积
		auto_start : true, // 选择文件后自动上传，若关闭需要自己绑定事件触发上传
		unique_names : false,
		domain : dw_domain,
		init : {
			'FileUploaded' : function(up, file, info) {
				$.ajax({
					url : base + "/upload/add",
					data : info,
					type : "POST",
					success : function() {
						layer.alert("上传成功");
					}
				});
			}
		}
	});
	}}}